package com.liziguo.lzgfp.client;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.liziguo.lzgfp.system.codec.AESCodec;
import com.liziguo.lzgfp.system.handler.ForWardHandler;
import com.liziguo.lzgfp.system.handler.TemporaryStorageHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.LocalDateTime;
import java.util.concurrent.CountDownLatch;

/**
 * @Author: Liziguo
 * @Date: 2021/12/24 21:01
 */
public class ClientHandler extends SimpleChannelInboundHandler<JSONObject> {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        final JSONObject jsonObject = new JSONObject();
        jsonObject.put("type", "client");
        jsonObject.put("key", Globale.key);
        ctx.channel().writeAndFlush(jsonObject).sync();
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, JSONObject jsonObject) throws Exception {
        logger.trace(jsonObject.toJSONString());
        switch (jsonObject.getString("type")) {
//            case null -> {
//            }
            case "ping" -> {
            }
            case "msg" -> {
                final String message = jsonObject.getString("message");
                if (message != null) {
                    logger.info(message);
                }
            }
            case "router" -> {
                final JSONArray router = jsonObject.getJSONArray("router");
                for (int i = 0; i < router.size(); i++) {
                    final JSONObject route = router.getJSONObject(i);
                    logger.info("加载路由:{}:{} -> {}:{}", Globale.SERVER_HOST, route.get("serverport"), route.getString("localhost"), route.getInteger("localport"));
                }
                logger.info("路由加载完毕");
            }
            case "forward" -> {
                final String forKey = jsonObject.getString("forKey");
                final String localhost = jsonObject.getString("localhost");
                final int localport = jsonObject.getInteger("localport");

                logger.info("接收到一个forward请求{}:{}", localhost, localport);

                CountDownLatch countDownLatch = new CountDownLatch(2);

                final Bootstrap toLocalBootstrap = new Bootstrap();
                toLocalBootstrap.group(Globale.workerGroup)
                        .channel(NioSocketChannel.class)
                        .handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel socketChannel) throws Exception {
                                socketChannel.pipeline().addLast(new TemporaryStorageHandler());
                                countDownLatch.countDown();
                            }
                        });
                ChannelFuture toLocal = toLocalBootstrap.connect(localhost, localport);
                toLocal.addListener(future -> {
                    if (future.isSuccess()) {
                        logger.debug("转发通道->连接本地服务器成功{}:{}", localhost, localport);
                    } else {
                        logger.error("转发通道->连接本地服务器失败{}:{}", localhost, localport);
                        logger.error(future.cause().getMessage());
                    }
                });


                final Bootstrap toServerBootstrap = new Bootstrap();
                toServerBootstrap.group(Globale.workerGroup)
                        .channel(NioSocketChannel.class)
                        .handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel socketChannel) throws Exception {
                                final ChannelPipeline pipeline = socketChannel.pipeline();
                                pipeline.addLast(new LengthFieldPrepender(3));
                                pipeline.addLast(new LengthFieldBasedFrameDecoder(1024 * 1024, 0, 3, 0, 3));
                                pipeline.addLast(new StringEncoder());
                                pipeline.addLast(new StringDecoder());
                                pipeline.addLast(new ChannelInboundHandlerAdapter() {
                                    @Override
                                    public void channelActive(ChannelHandlerContext ctx) throws Exception {
                                        ctx.pipeline().remove(this);
                                        ctx.channel().write("lzgfp-for");
                                        ctx.channel().write(forKey);
                                        ctx.pipeline().remove(StringEncoder.class);
                                        ctx.pipeline().remove(StringDecoder.class);
//                                                ctx.pipeline().remove(LengthFieldPrepender.class);
//                                                ctx.pipeline().remove(LengthFieldBasedFrameDecoder.class);
                                        ctx.pipeline().addLast(new AESCodec(Globale.aesKey));
                                        ctx.pipeline().addLast(new TemporaryStorageHandler());
                                        ctx.channel().flush();
                                        countDownLatch.countDown();
                                    }
                                });
                            }
                        });
                ChannelFuture toServer = toServerBootstrap.connect(Globale.SERVER_HOST, Globale.SERVER_PORT);
                toServer.addListener(future -> {
                    if (future.isSuccess()) {
                        logger.debug("转发通道->连接远程服务器成功{}:{}", Globale.SERVER_HOST, Globale.SERVER_PORT);
                    } else {
                        logger.error("转发通道->连接远程服务器失败{}:{}", Globale.SERVER_HOST, Globale.SERVER_PORT);
                        logger.error(future.cause().getMessage());
                    }
                });

                ctx.channel().eventLoop().execute(() -> {
                    try {
                        if (toLocal.await().isSuccess() && toServer.await().isSuccess()) {
                            countDownLatch.await();
                            toLocal.channel().pipeline().addLast(new ForWardHandler(toServer.channel()));
                            toServer.channel().pipeline().addLast(new ForWardHandler(toLocal.channel()));
                            logger.info("开启转发通道成功{} {}", toLocal.channel().remoteAddress(), toServer.channel().remoteAddress());
                        } else {
                            logger.error("开启转发通道失败{} {}", toLocal.channel().remoteAddress(), toServer.channel().remoteAddress());
                            toServer.channel().close();
                            toLocal.channel().close();
                        }
                    } catch (InterruptedException e) {
                        logger.error("开启转发通道失败{} {}", toLocal.channel().remoteAddress(), toServer.channel().remoteAddress());
                        toServer.channel().close();
                        toLocal.channel().close();
                        throw new RuntimeException(e);
                    }
                });

            }
            case "disconnect" -> {
                int val = jsonObject.getIntValue("time");
                logger.info("收到断开连接请求");

                Globale.setNextAutoReconnect(val);
                ctx.close();
            }
            default -> logger.warn("接收到未知指令 可能是客户端版本过低 请更新到最新版");
        }

    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof final IdleStateEvent event) {
            if (event.state() == IdleState.READER_IDLE) {
                // 在规定时间内没有收到客户端的上行数据, 主动断开连接
                ctx.disconnect();//TCP调用close UDP调用disconnect
                logger.info("服务器心跳超时{}", ctx.channel());
            } else if (event.state() == IdleState.WRITER_IDLE) {
                final JSONObject jsonObject = new JSONObject();
                jsonObject.put("type", "ping");
                jsonObject.put("ping", LocalDateTime.now());
                ctx.channel().writeAndFlush(jsonObject);
            }
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        logger.info("网络连接中断");
        ctx.fireChannelInactive();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        logger.error(cause.getMessage());
        cause.printStackTrace();
        ctx.close();
    }
}
